#include "pch.h"

#include "core/config.h"
#include "iosystem/filesystem.h"

#ifdef __WIN32__

namespace IO
{

	///
	FileSystem::Handle FileSystem::Open(const Helper::String &str, IO::Stream::AccessMode access, IO::Stream::AccessPattern pattern)
	{
		DWORD orAccess = 0, disposition = 0, shareMode = 0, flags = 0;
		const Helper::String & orPath = str;

		switch(access)
		{
		case Stream::ReadAccess:
			orAccess = GENERIC_READ;
			disposition = OPEN_EXISTING;
			shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
			break;

		case Stream::WriteAccess:
			orAccess = GENERIC_WRITE;
			disposition = CREATE_ALWAYS;
			shareMode = FILE_SHARE_READ;
			break;

		case Stream::ReadWriteAccess:
			orAccess = GENERIC_READ | GENERIC_WRITE;
			disposition = OPEN_ALWAYS;
			shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
			break;
		}
		switch(pattern)
		{
		case Stream::Random:
			flags |= FILE_FLAG_RANDOM_ACCESS;
			break;

		case Stream::Sequential:
			flags |= FILE_FLAG_SEQUENTIAL_SCAN;
			break;

		}

		Handle file = CreateFile(orPath.GetAsCharPtr(),
			orAccess,
			shareMode,
			0,
			disposition,
			flags,
			NULL);

		if(file != INVALID_HANDLE_VALUE)
		{
			if(Stream::AppendAccess == access)
				SetFilePointer(file, 0, NULL, FILE_END);

			return file;
		}
		else
			return 0;
	}


	///
	void FileSystem::Close(FileSystem::Handle file)
	{
		CloseHandle(file);
	}





	///
	void FileSystem::Write(FileSystem::Handle file, const void * buffer,  int bytes)
	{
		DWORD written;

		WriteFile(file, buffer, bytes, &written, NULL);
	}


	///
	int FileSystem::Read(FileSystem::Handle file, void * buffer, int bytes)
	{
		DWORD read;

		ReadFile(file, buffer, bytes, &read, NULL);

		return read;
	}


	///
	void FileSystem::Seek(FileSystem::Handle file, int offset, IO::Stream::SeekOrigin origin)
	{
		DWORD method;

		switch(origin)
		{
		case Stream::Begin:
			method = FILE_BEGIN;
			break;

		case Stream::Current:
			method = FILE_CURRENT;
			break;

		case Stream::End:
			method = FILE_END;
			break;

		default:
			method = FILE_BEGIN;
			break;
		}

		SetFilePointer(file, offset, NULL, method);
	}


	///
	int FileSystem::Tell(FileSystem::Handle file)
	{
		return SetFilePointer(file, 0, NULL, FILE_CURRENT);
	}


	///
	void FileSystem::Flush(FileSystem::Handle file)
	{
		FlushFileBuffers(file);
	}


	///
	bool FileSystem::Eof(FileSystem::Handle file)
	{
		DWORD pos = SetFilePointer(file, 0, NULL, FILE_CURRENT);
		DWORD size = ::GetFileSize(file, NULL);

		return pos >= size;
	}


	///
	int FileSystem::GetFileSize(FileSystem::Handle file)
	{
		return ::GetFileSize(file, NULL);
	}


	///
	void FileSystem::SetReadOnly(const Helper::String & path, bool readonly)
	{
		DWORD attrs = GetFileAttributes(path.GetAsCharPtr());

		if(readonly)
			attrs |= FILE_ATTRIBUTE_READONLY;
		else
			attrs &= ~FILE_ATTRIBUTE_READONLY;

		SetFileAttributes(path.GetAsCharPtr(), attrs);
	}


	///
	bool FileSystem::IsReadOnly(const Helper::String & path)
	{
		DWORD attrs = GetFileAttributes(path.GetAsCharPtr());

		return (attrs & FILE_ATTRIBUTE_READONLY);
	}


	///
	bool FileSystem::DeleteFile(const Helper::String & path)
	{
		return (::DeleteFileA(path.GetAsCharPtr()) != 0);
	}


	///
	bool FileSystem::DeleteDirectory(const Helper::String & path)
	{
		return (::RemoveDirectoryA(path.GetAsCharPtr()) != 0);
	}


	///
	bool FileSystem::FileExists(const Helper::String & path)
	{
		DWORD attrs = GetFileAttributes(path.GetAsCharPtr());

		if((attrs != -1) && ((FILE_ATTRIBUTE_DIRECTORY & attrs) == 0))
			return true;
		else
			return false;
	}


	///
	bool FileSystem::DirectoryExists(const Helper::String &path)
	{
		DWORD attrs = GetFileAttributes(path.GetAsCharPtr());

		if((attrs != -1) && ((FILE_ATTRIBUTE_DIRECTORY & attrs) != 0))
			return true;
		else
			return false;
	}


	///
	void FileSystem::SetFileWriteTime(const Helper::String & path, IO::FileTime time)
	{
		Handle file = FileSystem::Open(path, Stream::ReadWriteAccess, Stream::Sequential);

		if(file != 0)
		{
			SetFileTime(file, NULL, NULL, &time.m_time);

			FileSystem::Close(file);
		}
	}


	///
	IO::FileTime FileSystem::GetFileWriteTime(const Helper::String &path)
	{
		IO::FileTime result;

		Handle file = FileSystem::Open(path, Stream::ReadAccess, Stream::Sequential);

		if(file != 0)
		{
			GetFileTime(file, NULL, NULL, &result.m_time);

			FileSystem::Close(file);
		}

		return result;
	}


	///
	bool FileSystem::CreateDirectory(const Helper::String &path)
	{
		return (::CreateDirectoryA(path.GetAsCharPtr(), NULL) != 0);
	}





	///
	Helper::Array<Helper::String> FileSystem::ListFiles(const Helper::String & dirpath, const Helper::String & pattern)
	{
		Helper::Array<Helper::String> result;

		Helper::String pathpatt = dirpath + "/" + pattern;

		WIN32_FIND_DATA findData;

		Handle file;

		file = FindFirstFile(pathpatt.GetAsCharPtr(), &findData);

		if(INVALID_HANDLE_VALUE != file)
		{
			do
			{
				if((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
					result.Add(findData.cFileName);
			}
			while(FindNextFile(file, &findData) != 0);

			FindClose(file);
		}

		return result;
	}


	///
	Helper::Array<Helper::String> FileSystem::ListDirectories(const Helper::String & dirpath, const Helper::String & pattern)
	{
		Helper::Array<Helper::String> result;

		Helper::String pathpatt = dirpath + "/" + pattern;

		WIN32_FIND_DATA findData;

		Handle file;

		file = FindFirstFile(pathpatt.GetAsCharPtr(), &findData);

		if(INVALID_HANDLE_VALUE != file)
		{
			do
			{
				if(((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) &&
					(strcmp(findData.cFileName, "..") != 0) &&
					(strcmp(findData.cFileName, ".") != 0))
					result.Add(findData.cFileName);
			}
			while(FindNextFile(file, &findData) != 0);

			FindClose(file);
		}

		return result;
	}





	///
	Helper::String FileSystem::GetUserDirectory()
	{
		char buffer[260] = { 0 };

		SHGetFolderPath(NULL,
			CSIDL_PERSONAL | CSIDL_FLAG_CREATE,
			NULL,
			0,
			buffer);

		Helper::String result = buffer;

		result.ConvertBackslashes();

		return Helper::String("file:///") + result;
	}


	///
	Helper::String FileSystem::GetAppDataDirectory()
	{
		char buffer[260] = { 0 };

		SHGetFolderPath(NULL,
			CSIDL_APPDATA | CSIDL_FLAG_CREATE,
			NULL,
			0,
			buffer);

		Helper::String result = buffer;

		result.ConvertBackslashes();

		return Helper::String("file:///") + result;
	}


	///
	Helper::String FileSystem::GetTempDirectory()
	{
		char buffer[260] = { 0 };

		GetTempPath(sizeof(buffer), buffer);

		Helper::String result = buffer;

		result.ConvertBackslashes();

		result.TrimRight("/");

		return Helper::String("file:///") + result;
	}


	///
	Helper::String FileSystem::GetHomeDirectory()
	{
		char buffer[260] = { 0 };

		GetModuleFileName(NULL, buffer, sizeof(buffer));

		Helper::String result = buffer;

		result.ConvertBackslashes();

		Helper::String pathToDir = result.ExtractLastDirName();

		if(strcmp(pathToDir.GetAsCharPtr(), "win32") == 0)
		{
			Helper::String home = result.ExtractDirName();

			home = home.ExtractDirName();

			home = home.ExtractDirName();

			return Helper::String("file:///") + home;
		}
		else
		{
			Helper::String home = result.ExtractDirName();

			return Helper::String("file:///") + home;
		}
	}


	///
	Helper::String FileSystem::GetBinDirectory()
	{
		char buffer[260] = { 0 };

		GetModuleFileName(NULL, buffer, sizeof(buffer));

		Helper::String result = buffer;

		result.ConvertBackslashes();

		result = result.ExtractDirName();

		result.TrimRight("/");

		return Helper::String("file:///") + result;
	}


	///
	Helper::String FileSystem::GetProgramsDirectory()
	{
		char buffer[260] = { 0 };

		SHGetFolderPath(NULL,
			CSIDL_PROGRAM_FILES,
			NULL,
			0,
			buffer);

		Helper::String result = buffer;

		result.ConvertBackslashes();

		return Helper::String("file:///") + result;
	}





	/// WINDOWS ONLY!
	bool FileSystem::IsDeviceName(const Helper::String & str)
	{
		if(str.Length() == 1)
		{
			uchar c = str[0];

			if(((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')))
				return true;
		}

		return false;
	}

}

#endif // __WIN32__